package com.ling.feign.plus.support;

import com.google.gson.Gson;
import com.ling.common.ResultCode;
import com.ling.common.exception.BaseBusinessException;
import com.ling.common.exception.DefaultBusinessException;
import com.ling.common.exception.ParamException;
import com.ling.feign.plus.FeignExceptionConstant;
import com.ling.feign.plus.context.FeignEnvironmentAware;
import com.ling.feign.plus.model.BusinessExceptionModel;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;

/**
 * feign-server端
 * 当服务内报错
 * 进行业务异常信息的封装处理
 * 并返回给Feign
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FeignServerBusinessExceptionResolver extends DefaultErrorAttributes {

    private static final Logger log = LoggerFactory.getLogger(FeignServerBusinessExceptionResolver.class);
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {
        log.error("feign resolveException", ex);
        super.resolveException(request, response, handler, ex);
        //本ExceptionResolver只参与Feign服务间调用的异常问题
        //其他情况不处理
        if (!feignServerExceptionResolverCheck(request)) {
            return null;
        }
        return resolveException0(response, ex);
    }

    /**
     * 判断是否是Feign服务间调用
     */
    private boolean feignServerExceptionResolverCheck(HttpServletRequest request) {
        String feignServer = request.getHeader(FeignExceptionConstant.FEIGN_CALLER_HEADER);
        return !StringUtils.isBlank(feignServer);
    }

    private ModelAndView resolveException0(HttpServletResponse response,
                                           Exception exception) {

        //服务提供方的服务名称
        response.addHeader(FeignExceptionConstant.FEIGN_SERVER, FeignEnvironmentAware.getApplicationName());
        //提供服务方抛出的业务异常类名
        response.addHeader(FeignExceptionConstant.FEIGN_SERVER_THROWABLE_CLAZZ, exception.getClass().getName());

        Integer code;
        String msg;
        String exceptionClassName;
        StackTraceElement[] stackTrace = null;
        //已定义的业务异常
        if (exception instanceof BaseBusinessException) {
            BaseBusinessException baseException = (BaseBusinessException) exception;
            code = baseException.getCode();
            msg = baseException.getMsg();
            exceptionClassName = baseException.getClass().getName();
            //其他异常默认转为 DefaultException
        } else if (exception instanceof MethodArgumentNotValidException) {
            code = ResultCode.PARAM_ERROR.getCode();
            MethodArgumentNotValidException argumentNotValidException = (MethodArgumentNotValidException) exception;
            List<ObjectError> errors = argumentNotValidException.getBindingResult().getAllErrors();
            if (errors.size() > 0) {
                msg = errors.get(0).getDefaultMessage();
            } else {
                msg = ResultCode.PARAM_ERROR.getMsg();
            }
            exceptionClassName = ParamException.class.getName();
            stackTrace = new StackTraceElement[]{exception.getStackTrace()[0]};
        } else {
            code = ResultCode.INTERNAL_SERVER_ERROR.getCode();
            msg = ResultCode.INTERNAL_SERVER_ERROR.getMsg();
            exceptionClassName = DefaultBusinessException.class.getName();
            stackTrace = new StackTraceElement[]{exception.getStackTrace()[0]};
        }

        String exceptionJson =
                new Gson()
                        .toJson(
                                new BusinessExceptionModel(exceptionClassName)
                                        .setCode(code)
                                        .setMsg(msg)
                                        .setStackTrace(stackTrace)
                        );
        try {
            exceptionJson = URLEncoder.encode(exceptionJson, "UTF-8");
        } catch (UnsupportedEncodingException ignored) {

        }

        response.addHeader(FeignExceptionConstant.FEIGN_SERVER_BUSINESS_EXCEPTION, exceptionJson);

        try {
            response.sendError(HttpServletResponse.SC_EXPECTATION_FAILED, exception.getMessage());
        } catch (IOException ignored) {

        }
        //不能返回 null 不然servlet会走异常逻辑 status会变成500 而不是上面设置的417
        return new ModelAndView();
    }

}
